/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2012 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package info.geometrixx.outdoors.core.product;

import info.geometrixx.outdoors.core.util.GeoHelper;

import com.day.cq.wcm.api.NameConstants;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.designer.Designer;
import com.day.cq.wcm.api.designer.Style;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class GeoProduct {

    // ----< path constants >--------------------------------------------------

    public static final String TYPE_PRODUCT = "geometrixx-outdoors/components/product";
    public static final String PATH_VARIANT = "variant";
    public static final String PATH_IMAGE   = "image";
    
    // ----< property name constants >-----------------------------------------

    public static final String PN_TITLE       = NameConstants.PN_TITLE;
    public static final String PN_DESCRIPTION = NameConstants.PN_DESCRIPTION;
    public static final String PN_IDENTIFIER  = "identifier";
    public static final String PN_PRICE       = "price";
    public static final String PN_IS_ONE_SIZE = "isOneSize";
    public static final String PN_SIZES       = "sizes";
    public static final String PN_QTT_MIN     = "qttMin";
    public static final String PN_QTT_MAX     = "qttMax";

    // ----< static objects >--------------------------------------------------

    private static final Logger LOGGER = LoggerFactory.getLogger(GeoProduct.class);

    // ----< protected final members >-----------------------------------------

    protected final ResourceResolver resourceResolver;
    protected final PageManager      pageManager;
    protected final Designer         designer;
    protected final Resource         productResource;
    protected final ValueMap         productProperties;
    protected final GeoProduct       productParent;
    protected final Page             productPage;
    protected final ValueMap         pageProperties;
    protected final Style            productStyle;

    // ----< members that need initialization >--------------------------------

    private String   title       = null;
    private String   description = null;
    private String   identifier  = null;
    private String   price       = null;
    private String   brand       = null;
    private boolean  initSize    = false; // Tells if isOneSize and sizes have already been initialized
    private boolean  isOneSize   = false;
    private String[] sizes       = null;
    private boolean  initQtt     = false; // Tells if qttMin and qttMax have already been initialized
    private int      qttMin      = 0;
    private int      qttMax      = 0;
    private boolean  initVariant = false; // Tells if productVariant has already been initialized
    private List<GeoProduct> productVariant = null;

    // ----< constructors >----------------------------------------------------

    /**
     * Creates a new <code>GeoProduct</code> instance from the provided resource.
     * This object contains some optimization, making that the various properties are only actually retrieved when
     * really needed, and are also cached.
     * @param resource The resource to build the product from.
     */
    public GeoProduct(Resource resource) {
        if (resource == null) {
            LOGGER.error("Provided resource is null");
        }
        if (!resource.isResourceType(TYPE_PRODUCT)) {
            LOGGER.error(
                "Provided resource is not a product resource type.\n" +
                "Provided resource type: " + resource.getResourceType() + "\n" +
                "Needed resource type: " + TYPE_PRODUCT
            );
        }
        resourceResolver    = resource.getResourceResolver();
        pageManager         = resourceResolver.adaptTo(PageManager.class);
        designer            = resourceResolver.adaptTo(Designer.class);
        productResource     = resource;
        productProperties   = resource.adaptTo(ValueMap.class);
        productParent       = initParent();
        productPage         = pageManager.getContainingPage(resource);
        pageProperties      = (productPage == null) ? null : productPage.getProperties();
        productStyle        = designer.getStyle(resource);
    }

    /**
     * Creates a new <code>GeoProduct</code> instance from a parent or variant product.
     * Each product can be a variant from another product, or can contain several product variants.
     * Creating parent or variant products this way offers some optimization compared to the default constructor,
     * because several items can be reused that way.
     * @param resource  The resource to build the product from.
     * @param product   The related product (parent or variant of this new instance, depending on the
     *                  <code>isVariant</code> parameter).
     * @param isVariant If <code>true</code>, this new instance is a variant of the provided <code>product</code>.
     *                  If <code>false</code>, this new instance is the parent of the provided <code>product</code>.
     */
    protected GeoProduct(Resource resource, GeoProduct product, boolean isVariant) {
        resourceResolver    = product.resourceResolver;
        pageManager         = product.pageManager;
        designer            = product.designer;
        productResource     = resource;
        productProperties   = resource.adaptTo(ValueMap.class);
        productParent       = isVariant ? product : null;
        productPage         = product.productPage;
        pageProperties      = product.pageProperties;
        productStyle        = product.productStyle;
    }

    // ----< public methods >--------------------------------------------------

    public Resource getResource() {
        return productResource;
    }

    public ValueMap getProperties() {
        return productProperties;
    }

    /**
     * Tells if this product is a variant of another product
     * @return  <code>true</code> if it has a parent product.
     *          <code>false</code> otherwise.
     */
    public boolean isVariant() {
        return (productParent != null);
    }

    /**
     * Returns the product title, falling back respectively to the parent product and to the product page
     * (page title, title and page name).
     * @return The title.
     */
    public String getTitle() {
        // A null value is considered as non-initialized
        if (title == null) {
            LOGGER.debug("Initializing title");
            // Get value from product resource
            title = productProperties.get(PN_TITLE, String.class);
            // Get value from product parent
            if ((productParent != null) && GeoHelper.isEmpty(title))
                title = productParent.getProperties().get(PN_TITLE, String.class);
            // Get value from product page
            if (GeoHelper.isEmpty(title))
                title = GeoHelper.getPageTitle(productPage);
            // Make sure that the value is not null, to avoid initializing it again
            if (GeoHelper.isEmpty(title))
                title = "";
        }
        return title;
    }

    /**
     * Returns the product description, falling back respectively to the parent product and to the product page.
     * @return The description.
     */
    public String getDescription() {
        // A null value is considered as non-initialized
        if (description == null) {
            LOGGER.debug("Initializing description");
            // Get value from product resource
            description = productProperties.get(PN_DESCRIPTION, String.class);
            // Get value from product parent
            if ((productParent != null) && GeoHelper.isEmpty(description))
                description = productParent.getProperties().get(PN_DESCRIPTION, String.class);
            // Get value from product page
            if ((pageProperties != null) && (GeoHelper.isEmpty(description)))
                description = pageProperties.get(PN_DESCRIPTION, String.class);
            // Make sure that the value is not null, to avoid initializing it again
            if (GeoHelper.isEmpty(description))
                description = "";
        }
        return description;
    }

    /**
     * Returns the product identifier.
     * There is no fallback to the parent product as there is for other product properties, because each product
     * variant should have it's own identifier.
     * @return The identifier.
     */
    public String getIdentifier() {
        // A null value is considered as non-initialized
        if (identifier == null) {
            LOGGER.debug("Initializing identifier");
            // Get value from product resource
            identifier = productProperties.get(PN_IDENTIFIER, String.class);
            // Make sure that the value is not null, to avoid initializing it again
            if (GeoHelper.isEmpty(identifier))
                identifier = "";
        }
        return identifier;
    }

    /**
     * Returns the product price, falling back to the parent product.
     * @return The price.
     */
    public String getPrice() {
        // A null value is considered as non-initialized
        if (price == null) {
            LOGGER.debug("Initializing price");
            // Get value from product resource
            price = productProperties.get(PN_PRICE, String.class);
            // Get value from product parent
            if ((productParent != null) && GeoHelper.isEmpty(price))
                price = productParent.getProperties().get(PN_PRICE, String.class);
            // Make sure that the value is not null, to avoid initializing it again
            if (GeoHelper.isEmpty(price))
                price = "";
        }
        return price;
    }

    /**
     * Returns the product brand. Since all Geometrixx Outdoors products have the same brand,
     * it is taken from the root page title.
     * @return The brand.
     */
    public String getBrand() {
        // A null value is considered as non-initialized
        if (brand == null) {
            LOGGER.debug("Initializing brand name");
            // Get value from root page title
            if (productPage != null)
                brand = GeoHelper.getPageTitle(productPage.getAbsoluteParent(2));
            // Make sure that the value is not null, to avoid initializing it again
            if (GeoHelper.isEmpty(brand))
                brand = "";
        }
        return brand;
    }

    /**
     * Tells if this product exists in only one size, or if there can be various sizes (like for eg. S, M, L, XL).
     * @return <code>true</code> the product exists only in one size.
     */
    public boolean isOneSize() {
        if (!initSize) initSize();
        return isOneSize;
    }

    /**
     * Returns the different product sizes, or <code>null</code> if <code>isOneSize()</code> is <code>true</code>.
     * @return The sizes, or <code>null</code>.
     */
    public String[] getSizes() {
        if (!initSize) initSize();
        return sizes;
    }

    /**
     * Returns the minimum quantity of products it is possible to order.
     * The following will always be true: <code>getQttMin() <= getQttMax()</code>.
     * @return The minimum quantity.
     */
    public int getQttMin() {
        if (!initQtt) initQtt();
        return qttMin;
    }

    /**
     * Returns the maximum quantity of products it is possible to order.
     * The following will always be true: <code>getQttMin() <= getQttMax()</code>.
     * @return The maximum quantity.
     */
    public int getQttMax() {
        if (!initQtt) initQtt();
        return qttMax;
    }

    /**
     * Tells if this product has product variants.
     * @return <code>true</code> if there are variants.
     */
    public boolean hasVariant() {
        if (!initVariant) initVariant();
        return (productVariant != null);
    }

    /**
     * Returns the product variants, or <code>null</code> if <code>hasVariant()</code> is <code>false</code>.
     * @return A <code>List</code> of variants.
     */
    public List<GeoProduct> getVariant() {
        if (!initVariant) initVariant();
        return productVariant;
    }

    /**
     * Tells if this product has an image that points to a valid resource.
     * @return <code>true</code> if it has an image.
     */
    public boolean hasImage() {
        return (productResource.getChild(PATH_IMAGE) != null);
    }

    /**
     * The path of the image, or <code>null</code> if <code>hasImage()</code> is <code>false</code>.
     * @return The path.
     */
    public String getImagePath() {
        return hasImage() ? productResource.getPath() + "/" + PATH_IMAGE : null;
    }
    
    // ----< private initializers >--------------------------------------------
    
    private GeoProduct initParent() {
        LOGGER.debug("Initializing parent product");

        final Resource variantResource = productResource.getParent();
        if (variantResource != null) {
            final Resource parentResource = variantResource.getParent();
            if (parentResource != null && parentResource.isResourceType(TYPE_PRODUCT)) {
                return (new GeoProduct(parentResource, this, false));
            }
        }
        return null;
    }

    private void initVariant() {
        LOGGER.debug("Initializing product variants");
        initVariant = true;

        // If this product has a parent, it is already a variant
        if (productParent == null) {
            final Resource variantChild = productResource.getChild(PATH_VARIANT);
            if (variantChild != null) {
                Iterator<Resource> variantIterator = variantChild.listChildren();
                productVariant = new LinkedList();
                while (variantIterator.hasNext()) {
                    Resource variantItem = variantIterator.next();
                    if (variantItem.isResourceType(TYPE_PRODUCT)) {
                        productVariant.add(new GeoProduct(variantItem, this, true));
                    }
                }
                if (productVariant.size() == 0) {
                    productVariant = null;
                }
            }
        }
    }

    private void initSize() {
        LOGGER.debug("Initializing product sizes");
        initSize = true;

        // Get sizes from the product properties
        isOneSize = productProperties.get(PN_IS_ONE_SIZE, false);
        if (!isOneSize) {
            sizes = productProperties.get(PN_SIZES, String[].class);
        }
        // Get sizes from parent product properties
        if (!isOneSize && (sizes == null) && (productParent != null)) {
            isOneSize = productParent.getProperties().get(PN_IS_ONE_SIZE, false);
            if (!isOneSize) {
                sizes = productParent.getProperties().get(PN_SIZES, String[].class);
            }
        }
        // Get sizes from design properties
        if (!isOneSize && (sizes == null) && (productStyle != null)) {
            isOneSize = productStyle.get(PN_IS_ONE_SIZE, false);
            if (!isOneSize) {
                sizes = productStyle.get(PN_SIZES, String[].class);
            }
        }
        // Enforce coherent values
        if (isOneSize || (sizes == null) || (sizes.length == 0)) {
            sizes = null;
            isOneSize = true;
        }
    }

    private void initQtt() {
        LOGGER.debug("Initializing product quantities");
        initQtt = true;

        // Get qttMin from product product
        qttMin = productProperties.get(PN_QTT_MIN, 0);
        // Get qttMin from parent product properties
        if ((qttMin <= 0) && (productParent != null)) {
            qttMin = productParent.getProperties().get(PN_QTT_MIN, 0);
        }
        // Get qttMin from design properties
        if ((qttMin <= 0) && (productStyle != null)) {
            qttMin = productStyle.get(PN_QTT_MIN, 0);
        }
        // Enforce the validity of the value
        if (qttMin <= 0) {
            qttMin = 1;
        }

        // Get qttMax from product product
        qttMax = productProperties.get(PN_QTT_MAX, 0);
        // Get qttMax from parent product properties
        if ((qttMax <= 0) && (productParent != null)) {
            qttMax = productParent.getProperties().get(PN_QTT_MAX, 0);
        }
        // Get qttMax from design properties
        if ((qttMax <= 0) && (productStyle != null)) {
            qttMax = productStyle.get(PN_QTT_MAX, 0);
        }
        // Enforce coherent values
        if (qttMax <= qttMin) {
            qttMax = qttMin;
        }
    }
}
